package com.example.springboottaskscheduler.utils;

import com.example.springboottaskscheduler.task.CronTaskRunnable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * Created by IntelliJ IDEA2022.3.2
 * @Author: Tenghw
 * @Date: 2023/05/07  18:31
 * @Description: 定时任务注册类
 */
@Component
public class CronTaskRegistrar implements DisposableBean {
    private static final Logger log = LoggerFactory.getLogger(CronTaskRegistrar.class);


    /**
     * 缓存任务
     * key：具体的任务
     * value：注册定时任务后,返回的ScheduledFuture
     */
    private final Map<Runnable, CronTaskFuture> scheduledTasksMap = new ConcurrentHashMap<>(16);

    /**
     * 使用自定义的任务调度器配置
     */
    @Resource(name = "tenghw-taskScheduler")
    private TaskScheduler taskScheduler;

    /**
     * 获取自定义的任务调度器
     */
    public TaskScheduler getTaskScheduler() {
        return this.taskScheduler;
    }

    /**
     *  新增定时任务1
     *  若存在此任务，则删除此任务，重新新增这个任务
     * @param taskRunnable 执行的具体任务（taskRunnable实现Runnable）
     * @param cronExpression cron表达式
     */
    public void addCronTask(Runnable taskRunnable, String cronExpression) {
        //验证cron表达式是否正确
        boolean validExpression = CronExpression.isValidExpression(cronExpression);
        if (!validExpression) {
            throw new RuntimeException("cron表达式验证失败！");
        }
        //获取下次执行时间
        CronExpression parse = CronExpression.parse(cronExpression);
        LocalDateTime next = parse.next(LocalDateTime.now());
        if (Objects.nonNull(next)) {
            //定时任务下次执行的时间
            String format = next.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            log.info("定时任务下次执行的时间：{}", format);
        }
        //封装成 CronTask（cron任务）
        CronTask cronTask = new CronTask(taskRunnable, cronExpression);
        //添加任务
        this.addCronTask(cronTask);
    }

    /**
     * 新增定时任务2
     * @param cronTask ：CronTask用于在指定时间间隔内执行定时任务。
     *                  它是通过CronTrigger来实现的，CronTrigger是一个基于cron表达式的触发器，可以在指定的时间间隔内触发任务执行。
     **/
    private void addCronTask(CronTask cronTask) {
        if (Objects.nonNull(cronTask)) {
            //1若有这个任务，先删除这个任务。再新增该任务
            Runnable task = cronTask.getRunnable();
            String taskId = null;
            if (task instanceof CronTaskRunnable) {
                taskId = ((CronTaskRunnable) task).getTaskId();
                log.warn("该任务的taskId：{}",taskId);
            }
            //通过任务id获取缓存的任务，如果已包含该任务则删除，然后新增该任务
            Runnable taskCache = this.getTaskByTaskId(taskId);
            if (Objects.nonNull(taskCache) && this.scheduledTasksMap.containsKey(taskCache)) {
                //缓存中删除该任务
                this.removeCronTaskByTaskId(taskId);
            }
            //2注册定时任务到调度中心
            CronTaskFuture scheduledFutureTask = this.scheduleCronTask(cronTask);

            //3缓存定时任务；这里用ConcurrentHashMap做缓存
            this.scheduledTasksMap.put(task, scheduledFutureTask);

            //4可以将任务保存到数据库中……重新启动程序，然后加载数据库中的任务到缓存中……，这里就不写了。
        }
    }

    /** 注册定时任务到 任务调度器
     * @param cronTask cronTask
     * @return 注册定时任务后返回的 ScheduledFutureTask
     */
    private CronTaskFuture scheduleCronTask(CronTask cronTask) {
        //注册定时任务后记录的Future
        CronTaskFuture scheduledTask = new CronTaskFuture();
        //开启定时任务的真正方法
        scheduledTask.future = this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
//        scheduledTask.setThreadLocal(this.taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger()));
        return scheduledTask;
    }

    /**
     * 获取定时任务列表
     * @return List<CronTaskRunnable>
     */
    public List<CronTaskRunnable> getScheduledTasks() {
        // 封装返回结果
        List<CronTaskRunnable> tasks = new ArrayList<>();

        Set<Runnable> keySet = scheduledTasksMap.keySet();
        keySet.forEach(key -> {
            CronTaskRunnable task = new CronTaskRunnable();
            if (key instanceof CronTaskRunnable) {
                CronTaskRunnable taskParent = (CronTaskRunnable) key;
                BeanUtils.copyProperties(taskParent, task);
            }
            tasks.add(task);
        });
        return tasks.stream()
                .sorted(Comparator.comparing(CronTaskRunnable::getTaskId))
                .collect(Collectors.toList());
    }

    /** 根据任务id删除单个定时任务
     * @param taskId 任务id
     */
    public void removeCronTaskByTaskId(String taskId) {
        //通过任务id获取任务
        Runnable task = this.getTaskByTaskId(taskId);
        //需要通过任务id获取任务，然后再移除
        CronTaskFuture cronTaskFuture = this.scheduledTasksMap.remove(task);
        if (Objects.nonNull(cronTaskFuture)) {
            cronTaskFuture.cancel();
        }
    }

    /** 通过任务id获取任务。如果未查询到，则返回null
     * @param taskId 任务id
     * @return java.lang.Runnable
     **/
    private Runnable getTaskByTaskId(String taskId) {
        Assert.notNull(taskId, "任务id不能为空！");
        Set<Map.Entry<Runnable, CronTaskFuture>> entries = scheduledTasksMap.entrySet();
        //根据任务id获取该任务缓存
        Map.Entry<Runnable, CronTaskFuture> rcf = entries.stream().filter(rf -> {
            Runnable key = rf.getKey();
            String taskId1 = null;
            if (key instanceof CronTaskRunnable) {
                taskId1 = ((CronTaskRunnable) key).getTaskId();
            }
            return taskId.equals(taskId1);
        }).findAny().orElse(null);

        if (Objects.nonNull(rcf)) {
            return rcf.getKey();
        }
        return null;
    }

    /** 删除所有的定时任务
     *     DisposableBean是Spring框架中的一个接口，它定义了一个destroy()方法，
     *     用于在Bean销毁时执行清理工作。
     *     当一个Bean实现了DisposableBean接口时，
     *     Spring容器会在该Bean销毁时自动调用destroy()方法，
     *     以便进行一些清理工作，例如释放资源等。
     *     如果您的Bean需要在销毁时执行一些清理工作，
     *     那么实现DisposableBean接口是一个很好的选择。
     */
    @Override
    public void destroy() {
        //关闭所有定时任务
        for (CronTaskFuture task : this.scheduledTasksMap.values()) {
            task.cancel();
        }
        //清空缓存
        this.scheduledTasksMap.clear();
        log.warn("取消所有定时任务！");
        // 然后修改或删除数据库的任务（这里不写了）
    }
}
